﻿using System;
using System.Collections.Specialized;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web;
using Castle.MonoRail.ExtensionMethods.Helpers;
using Castle.MonoRail.Framework;

namespace Castle.MonoRail.ExtensionMethods {
	public class Utils: IUtils {
		private static readonly Regex controllerRx = new Regex("controller$", RegexOptions.IgnoreCase | RegexOptions.Compiled);

		public virtual string GetMethodName<T>(Expression<Action<T>> a) {
			return GetMethodCall(a).Method.Name;
		}

		private string GetUMethodName(LambdaExpression a) {
			var u = a.Body as UnaryExpression;
			var m = u.Operand as MethodCallExpression;
			var mm = m.Arguments[2] as ConstantExpression;
			return (mm.Value as MethodInfo).Name;
		}

		public virtual string GetMethodName<T>(Expression<Func<T, Action>> a) {
			return GetUMethodName(a);
		}

		public virtual string GetMethodName<T, R>(Expression<Func<T, Action<R>>> a) {
			return GetUMethodName(a);
		}

		public string GetMethodName<T, R1, R2>(Expression<Func<T, Action<R1, R2>>> a) {
			return GetUMethodName(a);
		}

		public string GetMethodName<T, R1, R2, R3>(Expression<Func<T, Action<R1, R2, R3>>> a) {
			return GetUMethodName(a);
		}

		public string GetMethodName<T, R1, R2, R3, R4>(Expression<Func<T, Action<R1, R2, R3, R4>>> a) {
			return GetUMethodName(a);
		}

		public virtual string GetControllerName<T>() where T : Controller {
			var name = from a in typeof (T).GetCustomAttributes<ControllerDetailsAttribute>()
			           where a.Name != null
			           select a.Name;
			return name.FirstOrDefault() ?? controllerRx.Replace(typeof(T).Name, "");
		}

		public virtual string GetControllerArea<T>() where T : Controller {
			var area = from a in typeof (T).GetCustomAttributes<ControllerDetailsAttribute>()
			           where a.Area != null
			           select a.Area;
			return area.FirstOrDefault();
		}

		public virtual object GetExpressionValue(Expression exp) {
			if (exp == null)
				return null;
			var genFunc = typeof (Func<>).MakeGenericType(exp.Type);
			var lambda = Expression.Lambda(genFunc, exp);
			return lambda.Compile().DynamicInvoke();
		}

		public virtual string GetDataBindPrefix(ParameterInfo p) {
			var db = p.GetCustomAttributes<DataBindAttribute>();
			if (db.Length == 0)
				return null;
			return db[0].Prefix;
		}

		public virtual IEnumerable<DictionaryEntry> GetValueNoDataBind(Expression exp, ParameterInfo param) {
			var value = GetExpressionValue(exp);
			if (value != null && value.GetType().IsArray) {
				var arr = (Array) value;
				for (int j = 0; j < arr.Length; j++)
					yield return new DictionaryEntry(string.Format("{0}[{1}]", param.Name, j), arr.GetValue(j));
			} else {
				yield return new DictionaryEntry(param.Name, value);
			}
		}

		public virtual IEnumerable<DictionaryEntry> GetValueDataBind(Expression exp, string prefix) {
			var o = GetExpressionValue(exp);
            if (o == null)
                return new DictionaryEntry[] { };
			return from prop in o.GetType().GetProperties()
						 let key = string.Format("{0}.{1}", prefix, prop.Name)
						 select new DictionaryEntry(key, prop.GetValue(o, null));
		}

		public virtual IEnumerable<DictionaryEntry> GetValue(MethodCallExpression body, Expression exp, ParameterInfo param) {
			var prefix = GetDataBindPrefix(param);
			return prefix == null ? GetValueNoDataBind(exp, param) : GetValueDataBind(exp, prefix);
		}

		/// <summary>
		/// Url encoded GetActionParameters
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="a"></param>
		/// <returns></returns>
		public virtual IDictionary GetEncodedActionParameters<T>(Expression<Action<T>> a) {
			return GetActionParameters(a).AsStrongDictionary<string, object>()
				.Where(kv => kv.Value != null)
				.Select(kv => new KeyValuePair<string, string>(kv.Key, HttpUtility.UrlEncode(kv.Value.ToString())))
				.ToDictionary(kv => kv.Key, kv => kv.Value);
		}

		public virtual IDictionary GetActionParameters<T>(Expression<Action<T>> a) {
			var body = GetMethodCall(a);
			var d = new OrderedDictionary();
			body.Method.GetParameters().ForEach((param, i) => {
				var exp = body.Arguments[i];
				if (exp != null)
					d.Add(GetValue(body, exp, param));
			});
			return d;
		}

		public virtual MethodCallExpression GetMethodCall(LambdaExpression a) {
			var body = a.Body as MethodCallExpression;
			if (body == null)
				throw new InvalidOperationException("Method call expected");
			return body;
		}
	}
}